package com.leinao.nio;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;

import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

/**
 *
 * @Description 文件或文件夹拷贝类
 * @author lizi
 * @date 2018/11/19 下午6:01
 * @Version 1.0
 *
 *
 * 在往目录拷贝文件之前，首先要保证目录已经存在。拷贝源目录（不论是否为空）都会生成目标目录。整个任务一定在preVisitDirectory()方法中执行。
 * visitFile()方式用来拷贝每个文件最佳的方法。
 * 在你拷贝目录或文件时，你需要决定是否要使用REPLACE_EXISTING 和COPY_ATTRIBUTES 选项。
 * 如果你想保存要拷贝的目录的属性，你需要在postVisitDirectory()方法中做此操作。
 * 如果你使用FOLLOW_LINKS选项，而在你的目录树中存在循环的链接指向父目录，这个循环将会在visitFileFailed()方法中抛出FileSystemLoopException异常。
 * 如果文件不允许访问，可以根据你自己的决定来返回FileVisitResult.CONTINUE或TERMINATE。
 * 如果你指定FOLLOW_LINKS选项，则可以跟踪符号链接文件。
 */
public class FileCopyTree implements FileVisitor<Path> {
    private static Logger logger = LoggerFactory.getLogger(FileCopyTree.class);

    private final Path copyFrom;
    private final Path copyTo;

    public FileCopyTree(Path copyFrom, Path copyTo) {
        this.copyFrom = copyFrom;
        this.copyTo = copyTo;
    }

    private void copySubTree(Path copyFrom, Path copyTo) throws IOException {
        logger.info("Copy sub copyFrom: {}, copyTo: {}", copyFrom, copyTo);
        try {
            Files.copy(copyFrom, copyTo, REPLACE_EXISTING, COPY_ATTRIBUTES);
        } catch (IOException e) {
            logger.error("copySubTree IOException", e);
            throw e;
        }
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        if (exc == null) {
            Path newDir = copyTo.resolve(copyFrom.relativize(dir));
            try {
                FileTime time = Files.getLastModifiedTime(dir);
                Files.setLastModifiedTime(newDir, time);
            } catch (IOException e) {
                logger.error("postVisitDirectory IOException, Unable to copy all attributes to: ", e);
                throw e;
            }
        } else {
            throw exc;
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        logger.info("Copy directory: {}", dir);
        Path newDir = copyTo.resolve(copyFrom.relativize(dir));
        try {
            Files.copy(dir, newDir, REPLACE_EXISTING, COPY_ATTRIBUTES);
        } catch (IOException e) {
            logger.error("preVisitDirectory IOException, Unable to create dir", e);
            throw e;
            //return FileVisitResult.SKIP_SUBTREE;
        }

        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        copySubTree(file, copyTo.resolve(copyFrom.relativize(file)));
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        if (exc instanceof FileSystemLoopException) {
            logger.error("visitFileFailed FileSystemLoopException, Cycle was detected: ", exc);
        } else {
            logger.error("visitFileFailed FileSystemLoopException, Error occurred, unable to copy: ", exc);
        }

        throw exc;
        //return FileVisitResult.CONTINUE;
    }
}
